#include <iostream>
#include <windows.h>

//读取文件数据
char *Readfilcontent(char *filename, DWORD *filesize)
{
    // CreateFileA打开一个文件并返回句柄，非零参数的含义：1，文件名 2，访问的权限，这里为读写 3，请求共享模式。对其属性或扩展属性的访问请求进行约束 4，仅打开存在的文件
    HANDLE hfile = CreateFileA(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
    if (hfile == INVALID_HANDLE_VALUE)
    {
        std::cout << "open file fail" << std::endl;
        return NULL;
    }
    DWORD dwRead;
    //获得文件的大小，参数：1，文件的句柄 2，指向双字地址，用于存储64位长度大小的前32位， 如果文件大小小于2^32，可以设为NULL
    DWORD dwSize = GetFileSize(hfile, &dwRead);
    *filesize = dwSize;
    char *buf = new char[dwSize];
    //将指定的地址区域置0，参数：1，需要置0的首地址 2，置0的个数
    RtlZeroMemory(buf, dwSize);
    //读取文件信息，参数：1，文件句柄 2，保存读入数据的缓冲区 3，要读入的字节数 4，指向实际读取字节数的指针 5，不理会，置0就好了
    ReadFile(hfile, buf, dwSize, &dwRead, 0);
    if (dwSize != dwRead)
    {
        std::cout << "read file erro" << std::endl;
        return NULL;
    }
    //一定要关闭句柄释放资源
    CloseHandle(hfile);
    return buf;
}

//存储文件

bool SaveFile(char *filename, char *buf, int len)
{
    HANDLE hfile = CreateFileA(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS, 0, 0);
    if (hfile == INVALID_HANDLE_VALUE)
    {
        std::cout << "SaveFile: open file erro" << std::endl;
        return NULL;
    }

    DWORD dwWrite; //保存写入文件中的字节数,
    // 在文件中重新设置读取位置， 参数：1， 文件句柄 2，偏移量（Dword）低双字节 3，偏移量， 高双字节， 如果不设置就直接按第二个参数来 4，宏设置， 用于表示当前取到读取位置之后， 偏移量应该怎么算， 本程序中的设定表示依然按照文章起始处算偏移量
    SetFilePointer(hfile, 0, 0, FILE_BEGIN);
    // 写入文件， 参数： 1，文件句柄 2，参数类型:指针,指向将写入文件的 数据缓冲区 3，要写入数据的字节数量。如写入零字节，表示什么都不写入 4，实际写入文件的字节数量
    WriteFile(hfile, buf, len, &dwWrite, 0);
    CloseHandle(hfile);
    return true;
}

//隐藏文件
bool Hide(char *secretFileName, char *bmpFileName)
{
    bool ret = false;
    DWORD dwBmpSize, dwSecretSize;

    //读取文件数据 返回读取的字符串， 参数：1， 文件名， 2，存储读出的大小
    //注意也可以使用readfile去读， 但是不能直接传入文件名， 需要传入文件句柄（需要先调用CreateA（）函数）
    char *pSecrite = Readfilcontent(secretFileName, &dwSecretSize);
    char *pBMP = Readfilcontent(bmpFileName, &dwBmpSize);

    //获取像素偏移量， 对于一个BMP文件来说， 前两个字节为文件格式， 紧接着的四个字节是文件大小， 第11个字节到第14个字节是像素的偏移位置
    //在这里我们是通过像素的偏移量获取文件位置， 然后在这些地方进行隐写， 这种影响是像素级别的不会被发现

    DWORD *lpFirstPointOffset = (DWORD *)(pBMP + 10);
    std::cout << "获得像素偏移位置:" << *lpFirstPointOffset << std::endl;

    // 获取游标， 进行文件写入
    char *pCurrentBMP = pBMP + *lpFirstPointOffset;
    char *pCurrentSecret = pSecrite;
    //先保存一下隐写文件的大小（在图片像素开始的4个字节上）(占用像素需要是3的整数倍， 这里占用4， 那么我们前进6位)
    *((DWORD *)pCurrentBMP) = dwSecretSize;
    pCurrentBMP += 6;
    //下面开始保存文件（注意因为每一个像素点是3个字， 所以我们也要以3个字节为单位存数据以保证不破坏图片像素）
    //插入方式， 个一个像素插入一个数据， 这样对图片的破坏是像素级的， 不好察觉
    for (; (pCurrentBMP < pBMP + dwBmpSize) && (pCurrentSecret < pSecrite + dwSecretSize); pCurrentBMP += 6)
    {
        *pCurrentBMP = *pCurrentSecret;
        *(pCurrentBMP + 1) = *(pCurrentSecret + 1);
        *(pCurrentBMP + 2) = *(pCurrentSecret + 2);
        pCurrentSecret += 3;
    }
    //保存文件
    SaveFile(bmpFileName, pBMP, dwBmpSize);
    delete[] pBMP;
    delete[] pSecrite;
    return ret;
}

//恢复图片
bool Recovery(char *bmpfilename, char *secretfilname)
{

    bool ret = false;
    DWORD dwBMPsize;
    char *pBMP = Readfilcontent(bmpfilename, &dwBMPsize);
    //获取像素开始位置
    DWORD *pFirstPoint = (DWORD *)(pBMP + 10);
    //获取之前写入的位置（这里可能错误）
    DWORD dwSecritsize = *(DWORD *)(pBMP + *pFirstPoint);

    char *buf = new char[dwSecritsize];
    char *pCurrentBMp = pBMP + *pFirstPoint + 6;
    for (int i = 1; pCurrentBMp < pBMP + dwBMPsize && i <= dwSecritsize + dwSecritsize; pCurrentBMp += 6)
    {
        buf[i] = *pCurrentBMp;
        buf[i + 1] = *(pCurrentBMp + 1);
        buf[i + 2] = *(pCurrentBMp + 2);
        i += 3;
    }

    SaveFile(secretfilname, buf, dwSecritsize);
    delete[] pBMP;
    delete[] buf;
    return true;
}

int main(int argc, char const *argv[])
{

    if (argc < 3)
    {
        std::cout << "please input 3 arg" << std::endl;
        return 1;
    }

    if (strcmp(argv[1], "Encrypt") == 0)
    {
        Hide((char *)argv[2], (char *)argv[3]);
    }

    if (strcmp(argv[1], "Decrypt") == 0)
    {
        Recovery((char *)argv[2], (char *)argv[3]);
    }
    else
    {
        std::cout << "Error" << std::endl;
        return 1;
    }

    std::cout << "finish" << std::endl;
    return 0;
}
